﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace Faml.Frontend
{
    public class Compiler
    {
        private Parser parser;
        private PrecompilerContext context;

        public Compiler(OperationLibrary operationLibrary)
        {
            System.Diagnostics.Debug.Assert(operationLibrary != null, "Operation library is null");
            context = new PrecompilerContext(operationLibrary);
        }

        public OperationLibrary OperationLibrary
        {
            get { return context.OperationLibrary; }
        }

        public FamlExpression Precompile(Parser parser)
        {
            ParseDefinitions(parser);
            this.parser = parser;
            return ParseWith();
        }

        public void Reset()
        {
            context = new PrecompilerContext(context.OperationLibrary);
        }

        public PrecompilerContext Context
        {
            get
            {
                return context;
            }
        }

        public void ParseDefinitions(Parser parser)
        {
            Parser temp = this.parser;
            try
            {
                this.parser = parser;
                while (parser.MatchKeyword("def"))
                {
                    parser.MoveNext();
                    ParseDefinition();
                }
            }
            finally
            {
                this.parser = temp;
            }
        }

        private FamlExpression ParseWith()
        {
            if (parser.MatchKeyword("with"))
            {
                int marker = context.GetDefinitionsMarker();
                while (true)
                {
                    parser.MoveNext();
                    ParseDefinition();
                    if (!parser.MatchDelimiter(","))
                        break;
                }
                FamlExpression result = ParseWith();
                context.RemoveDefinitions(marker);
                return result;
            }
            else
                return ParseConditional();
        }

        private FamlDefinition ParseDefinition()
        {
            if (parser.TokenType != TokenType.Ident)
                Error("Definition name expected");
            string name = parser.TokenText; // Definition name
            if (context.ContainsName(name))
                Error("Duplicated definition name");
            if (parser.MoveNext() == TokenType.Delimiter && parser.TokenText == "(") 
            {   // Definition with parameters
                List<DefinitionParameter> parameters = ParseDefinitionParameters();
                if (!parser.MatchDelimiter("="))
                    Error("Expecting equal sign");
                parser.MoveNext();
                int marker = context.GetParametersMarker();
                context.AddParameters(parameters);
                FamlExpression body = ParseWith();
                context.RemoveParameters(marker);
                FamlDefinition md = new FamlDefinition(name, parameters, body);
                context.AddDefinition(md);
                return md;
            } 
            else if (parser.MatchDelimiter("="))
            {   // Definition without parameters
                parser.MoveNext();
                FamlDefinition md = new FamlDefinition(name, ParseWith());
                context.AddDefinition(md);
                return md;
            }
            else
                Error("Syntax errors in definition");
            return null;
        }

        private List<DefinitionParameter> ParseDefinitionParameters()
        {
            List<DefinitionParameter> parameters = new List<DefinitionParameter>();
            while (true)
            {
                if (parser.MoveNext() != TokenType.Ident)
                    Error("Definition parameter name expected");
                parameters.Add(new DefinitionParameter(parser.TokenText));
                if (parser.MoveNext() == TokenType.Delimiter && parser.TokenText == ")")
                    break;
                else if (!parser.MatchDelimiter(","))
                    Error("Syntax error in definition");
            }
            parser.MoveNext();
            return parameters;
        }

        private FamlExpression ParseConditional()
        {
            FamlExpression result = ParseAndOr();
            if (parser.MatchDelimiter("?"))
            {
                parser.MoveNext();
                FamlExpression trueBranch = ParseAndOr();
                if (!parser.MatchDelimiter(":"))
                    Error("Colon expected in conditional operator");
                parser.MoveNext();
                FamlExpression falseBranch = ParseAndOr();
                result = new FamlConditionalExpression(result, trueBranch, falseBranch);
            }
            return result;
        }

        private FamlExpression ParseAndOr()
        {
            FamlExpression first = ParseComparison();
            while (parser.MatchOperator("&&") ||
                parser.MatchOperator("||"))
            {
                string op = parser.TokenText;
                parser.MoveNext();
                FamlExpression second = ParseComparison();
                first = new FamlBinaryExpression(first, second, op);
            }
            return first;
        }

        private FamlExpression ParseComparison()
        {
            FamlExpression first = ParsePlusMinus(parser);
            while (parser.MatchOperator("==") ||
                   parser.MatchOperator(">") ||
                   parser.MatchOperator("<") ||
                   parser.MatchOperator(">=") ||
                   parser.MatchOperator("<="))
            {
                string op = parser.TokenText;
                parser.MoveNext();
                FamlExpression second = ParsePlusMinus(parser);
                first = new FamlBinaryExpression(first, second, op);
            }
            return first;
        }

        private FamlExpression ParsePlusMinus(Parser parser)
        {
            FamlExpression first = ParseMulDiv(parser);
            while (parser.MatchOperator("+") || parser.MatchOperator("-"))
            {
                string op = parser.TokenText;
                parser.MoveNext();
                FamlExpression second = ParseMulDiv(parser);
                first = new FamlBinaryExpression(first, second, op);
            }
            return first;
        }

        private FamlExpression ParseMulDiv(Parser parser)
        {
            FamlExpression first = ParseMemberAccessAndIndexers();
            while (parser.MatchDelimiter("*") || parser.MatchDelimiter("/"))
            {
                string op = parser.TokenText;
                parser.MoveNext();
                FamlExpression second = ParseMemberAccessAndIndexers();
                first = new FamlBinaryExpression(first, second, op);
            }
            return first;
        }

        private FamlExpression ParseMemberAccessAndIndexers()
        {
            FamlExpression target = ParsePrimary();
            while (true) {
                if(parser.MatchDelimiter(".")) {            
                    if (parser.MoveNext() != TokenType.Ident)
                        Error("Method or field name expected");
                    string name = parser.TokenText;
                    if (parser.MoveNext() == TokenType.Delimiter && parser.TokenText == "(")
                        ParserException.Throw(parser, "Method calls are not supported");
                    FamlDictionaryExpression fde = target as FamlDictionaryExpression;
                    if (fde != null)
                    {
                        if (fde.ContainsField(name))
                            target = fde.GetField(name);
                        else
                            Error("No key " + name + " in dictionary");
                    } else
                        target = new FamlFieldExpression(target, name);
                } else if (parser.MatchDelimiter("[")) {
                    context.BeginIndexer(target);
                    try
                    {
                        List<FamlExpression> items = ParseSequence("]",true);
                        items.Insert(0, target);
                        target = new FamlFunctionCallExpression("subarray", items, context.OperationLibrary);
                    }
                    finally
                    {
                        context.EndIndexer();
                    }
                } else
                    break;
            }
            return target;
        }

        private FamlExpression ParsePrimary()
        {
            if (parser.TokenType == TokenType.Integer) // Integer constant
            {
                FamlExpression result = new FamlConstant<int>(parser.IntegerValue);
                parser.MoveNext();
                return result;
            }
            else if (parser.TokenType == TokenType.Double) // Double precision floating point constant
            {
                FamlExpression result = new FamlConstant<double>(parser.DoubleValue);
                parser.MoveNext();
                return result;
            }
            else if (parser.TokenType == TokenType.String2) // String with two quotes
            {
                FamlExpression result = new FamlConstant<string>(parser.TokenText);
                parser.MoveNext();
                return result;
            }
            else if (parser.TokenType == TokenType.DateTime) // DateTime
            {
                FamlExpression result = new FamlConstant<DateTime>(parser.CurrentToken.DateTime);
                parser.MoveNext();
                return result;
            }
            else if (parser.TokenType == TokenType.TimeSpan) // TimeSpan
            {
                FamlExpression result = new FamlConstant<TimeSpan>(parser.CurrentToken.TimeSpan);
                parser.MoveNext();
                return result;
            }
            else if (parser.MatchDelimiter("(")) // Expression in braces
            {
                List<FamlLambdaParameter> lambdaArgs = TryParseLambdaExpressionArgs(parser);
                if (lambdaArgs != null)
                    return ParseLambdaExpression(parser, lambdaArgs);
                parser.MoveNext();
                FamlExpression result = ParseConditional();
                if (parser.TokenType != TokenType.Delimiter || parser.TokenText != ")")
                    Error("Missing closing brace");
                parser.MoveNext();
                return result;
            }
            else if (parser.TokenType == TokenType.Ident) // Identifier
            {
                List<FamlLambdaParameter> lambdaArgs = TryParseLambdaExpressionArgs(parser);
                if (lambdaArgs != null)
                    return ParseLambdaExpression(parser, lambdaArgs);
                string ident = parser.TokenText;
                if (parser.MoveNext() == TokenType.Delimiter && parser.TokenText == "(")
                {
                    List<FamlExpression> args = ParseArgs(parser);
                    DefinitionParameter par = context.LookupDefinitionParameter(ident);
                    if (par != null)
                        throw new NotImplementedException("Lambda parameters are not supported");
                    else
                    {
                        FamlDefinition def = context.LookupDefinition(ident, args.Count);
                        if (def != null)
                            return def.Expand(args);
                        else
                            return new FamlFunctionCallExpression(ident, args, context.OperationLibrary);
                    }
                    // return context.Invoke(ident, args.ToArray());
                }
                else
                { // Parameter or macro
                    FamlExpression result;
                    FamlDefinition def = context.LookupDefinition(ident, 0);
                    if (def != null)
                        result = def.Body;
                    else
                    {
                        DefinitionParameter par = context.LookupDefinitionParameter(ident);
                        if (par != null)
                            result = new FamlDefinitionParameterExpression(par);
                        else
                        {
                            FamlLambdaParameter mpar = context.LookupLambdaParameter(ident);
                            if (mpar != null)
                                result = new FamlLambdaParameterExpression(mpar);
                            else
                                throw new Exception("Unknown identifier " + ident);
                        }
                    }
                    return result;
                }
            }
            else if (parser.MatchDelimiter("{"))
                return ParseInlineArrayOrDictionary();
            else if (parser.MatchOperator("-"))
            {
                parser.MoveNext();
                return new FamlUnaryExpression(UnaryOperations.Negate, ParsePrimary());
            }
            else if (parser.MatchOperator("+"))
            {
                parser.MoveNext();
                return new FamlUnaryExpression(UnaryOperations.Positive, ParsePrimary());
            }
            else
                Error("Unexpected token");
            return null;
        }

        private FamlExpression ParseInlineArrayOrDictionary()
        {
            bool isArray = true;
            Stack<Token> tokens = new Stack<Token>();
            tokens.Push(parser.CurrentToken);
            if (parser.MoveNext() == TokenType.Ident)
            {
                tokens.Push(parser.CurrentToken);
                if (parser.MoveNext() == TokenType.Delimiter && parser.TokenText == "=")
                    isArray = false;
            }
            while (tokens.Count > 0)
                parser.PutBack(tokens.Pop());
            if (isArray)
                return ParseInlineArray();
            else
                return ParseDictionary();
        }

        private FamlDictionaryExpression ParseDictionary()
        {
            OrderedDictionary contents = new OrderedDictionary();
            if (parser.MoveNext() != TokenType.Delimiter || parser.TokenText != "}")
                while (true)
                {
                    if (parser.TokenType != TokenType.Ident)
                        Error("Expected dictionary field name");
                    string name = parser.TokenText;
                    if (contents.Contains(name))
                        Error("Duplicate field names in dictionary");
                    parser.MoveNext();
                    if (!parser.MatchDelimiter("="))
                        Error("Syntax error in dictionary definition - equal sign expected");
                    parser.MoveNext();
                    FamlExpression expr = ParseWith();
                    contents.Add(name, expr);
                    if (parser.MatchDelimiter("}"))
                        break;
                    else if (parser.MatchDelimiter(","))
                        parser.MoveNext();
                    else
                        Error("Syntax error in dictionary definition");
                }
            parser.MoveNext();
            return new FamlDictionaryExpression(contents);
        }

        private FamlExpression ParseInlineArray()
        {
            List<FamlExpression> items = ParseSequence("}",false);
            return new FamlInlineArrayExpression(items);
        }

        private FamlExpression ParseLambdaExpression(Parser parser, List<FamlLambdaParameter> args)
        {
            context.BeginAddingLambdaParameters();
            context.AddParameters(args);
            FamlExpression body = ParseConditional();
            FamlExpression result = new FamlLambdaExpression(body, args);
            context.RemoveLambdaParameters(); // Pair call to BeginAddingLambdaParameters
            return result;
        }

        /// <summary>Tries to parse parameter declaration (with or without explicit type)</summary>
        /// <param name="tokens">Stack of tokens for possible rollback. Stack can be null</param>
        /// <returns>MacroLambdaParameter node or null if this is not a parameter declaration</returns>
        private FamlLambdaParameter TryParseParameterDeclaration(Stack<Token> tokens)
        {
            string paramName = parser.TokenText;
            string typeName = string.Empty;
            if (tokens != null)
                tokens.Push(parser.CurrentToken);
            if (parser.MoveNext() == TokenType.Ident)
            {
                typeName = paramName;
                paramName = parser.TokenText;
                if (tokens != null)
                    tokens.Push(parser.CurrentToken);
                parser.MoveNext();
            }
            try
            {
                if (typeName != String.Empty)
                    return new FamlLambdaParameter(paramName, context.LookupType(typeName));
                else
                    return new FamlLambdaParameter(paramName, context.GetTypeFromVariableName(paramName));
            }
            catch
            {
                return null;
            }

        }

        private List<FamlLambdaParameter> TryParseParametersDeclaration(Stack<Token> tokens)
        {
            List<FamlLambdaParameter> args = new List<FamlLambdaParameter>();
            if (parser.TokenType == TokenType.Ident)
                while (parser.TokenType == TokenType.Ident)
                {
                    FamlLambdaParameter pe = TryParseParameterDeclaration(tokens);
                    if (pe == null) // Not a parameter declaration 
                        break;
                    args.Add(pe);
                    if (parser.TokenType == TokenType.Delimiter)
                    {
                        if (parser.TokenText == ")") // Closing brace
                            break;
                        else if (parser.TokenText == ",") // Comma
                        {
                            if (tokens != null)
                                tokens.Push(parser.CurrentToken);
                            parser.MoveNext();
                        }
                        else
                            return null;
                    }
                    else // Not a delimiter -> syntax error in parameter list
                        return null;
                }
            else if (!parser.MatchDelimiter(")"))
                return null;
            // Skip closing brace and move to next token
            if (tokens != null)
                tokens.Push(parser.CurrentToken);
            parser.MoveNext();
            // Return list of arguments
            return args;
        }

        /// <summary>Looks forward to determine: is this a beginning of lambda expression?</summary>
        /// <param name="parser">Parser to read tokens from</param>
        /// <returns>List with arguments names or null if this is not a lambda expression</returns>
        private List<FamlLambdaParameter> TryParseLambdaExpressionArgs(Parser parser)
        {
            // Retrieved tokes to put back in case of not lambda expression
            Stack<Token> tokens = new Stack<Token>();
            // List of parameters
            List<FamlLambdaParameter> args = new List<FamlLambdaParameter>();

            if (parser.MatchDelimiter("(")) // List of args?
            {
                tokens.Push(parser.CurrentToken);
                parser.MoveNext();
                args = TryParseParametersDeclaration(tokens);
                if (args != null && parser.MatchOperator("=>"))
                {
                    parser.MoveNext();
                    return args;
                }
                while (tokens.Count > 0)
                    parser.PutBack(tokens.Pop());
                return null;
            }
            else if (parser.TokenType == TokenType.Ident) // Single arg?
            {
                FamlLambdaParameter pe = TryParseParameterDeclaration(tokens);
                if (pe != null)
                {
                    args.Add(pe);
                    if (parser.TokenType == TokenType.Operator && parser.TokenText == "=>")
                    {
                        parser.MoveNext(); // Move to the first token of lambda expression
                        return args;
                    }
                }
                while(tokens.Count > 0)
                    parser.PutBack(tokens.Pop());
                return null;
            }
            else // Definitely not a lambda expression
                return null;
        }

        private List<FamlExpression> ParseSequence(string closing,bool isIndexer)
        {
            List<FamlExpression> sequence = new List<FamlExpression>();
            parser.MoveNext();
            while (true)
            {
                if (parser.MatchDelimiter(closing))
                    break;
                if (isIndexer && parser.MatchDelimiter("*"))
                {
                    parser.MoveNext();
                    FamlExpression lengthExpr = new FamlFunctionCallExpression(this.Context.OperationLibrary,"length", context.CurrentArray, new FamlConstant<int>(context.CurrentIndex));
                    FamlLambdaParameter i = new FamlLambdaParameter("i",typeof(int));
                    FamlLambdaExpression identExpr = new FamlLambdaExpression(new FamlLambdaParameterExpression(i),i);
                    FamlFunctionCallExpression unfoldExpr = new FamlFunctionCallExpression(this.Context.OperationLibrary, "unfold", lengthExpr, identExpr);
                    sequence.Add(unfoldExpr);
                } else 
                    sequence.Add(ParseConditional());
                if (parser.MatchDelimiter(closing))
                    break;
                else if (!parser.MatchDelimiter(","))
                    Error("Sequence has mistakes");
                parser.MoveNext();
                if (isIndexer) // Move to next index if parsing indexer
                    context.NextIndex(); 
            }
            parser.MoveNext();
            return sequence;
        }

        private List<FamlExpression> ParseArgs(Parser parser)
        {
            return ParseSequence(")",false);
        }

        public FamlDefinition ParseDefinition(Parser mp)
        {
            Parser temp = parser;
            try
            {
                parser = mp;
                return ParseDefinition();
            }
            finally
            {
                parser = temp;
            }
        }

        private void Error(string message)
        {
            CompilerException.Throw(parser, message);
        }
    }
}